from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes
import json

class AliPay(object):

    """
    支付宝支付接口(PC端支付接口)
    """
    def __init__(self, appid, app_notify_url, app_private_key_path,
                 alipay_public_key_path, return_url, debug=False):
        self.appid = appid
        self.app_notify_url = app_notify_url
        self.app_private_key_path = app_private_key_path
        self.app_private_key = None
        self.return_url = return_url
        # 读取应用私钥
        try:
            with open(self.app_private_key_path, 'r', encoding='utf-8') as fp:
                key_content = fp.read().strip()
                if not key_content or len(key_content.split('\n')) < 3:
                    raise ValueError(
                        f"应用私钥文件格式错误: {self.app_private_key_path}\n"
                        f"密钥文件必须包含完整的PEM格式密钥（至少3行：开始标记、密钥内容、结束标记）\n"
                        f"请从支付宝开放平台获取应用私钥并正确填写到文件中。"
                    )
                self.app_private_key = RSA.importKey(key_content)
        except FileNotFoundError:
            raise FileNotFoundError(
                f"应用私钥文件不存在: {self.app_private_key_path}\n"
                f"请创建密钥文件并填入从支付宝开放平台获取的应用私钥。"
            )
        except ValueError as e:
            if "PEM file must have at least 3 lines" in str(e):
                raise ValueError(
                    f"应用私钥文件格式错误: {self.app_private_key_path}\n"
                    f"密钥文件内容不完整，必须包含完整的PEM格式密钥。\n"
                    f"请从支付宝开放平台获取应用私钥并正确填写到文件中。\n"
                    f"密钥格式示例：\n"
                    f"-----BEGIN RSA PRIVATE KEY-----\n"
                    f"密钥内容（多行base64编码）\n"
                    f"-----END RSA PRIVATE KEY-----"
                )
            raise

        self.alipay_public_key_path = alipay_public_key_path

        # 读取支付宝公钥
        try:
            with open(self.alipay_public_key_path, 'r', encoding='utf-8') as fp:
                key_content = fp.read().strip()
                if not key_content or len(key_content.split('\n')) < 3:
                    raise ValueError(
                        f"支付宝公钥文件格式错误: {self.alipay_public_key_path}\n"
                        f"密钥文件必须包含完整的PEM格式密钥（至少3行：开始标记、密钥内容、结束标记）\n"
                        f"请从支付宝开放平台获取支付宝公钥并正确填写到文件中。"
                    )
                self.alipay_public_key = RSA.importKey(key_content)
        except FileNotFoundError:
            raise FileNotFoundError(
                f"支付宝公钥文件不存在: {self.alipay_public_key_path}\n"
                f"请创建密钥文件并填入从支付宝开放平台获取的支付宝公钥。"
            )
        except ValueError as e:
            if "PEM file must have at least 3 lines" in str(e):
                raise ValueError(
                    f"支付宝公钥文件格式错误: {self.alipay_public_key_path}\n"
                    f"密钥文件内容不完整，必须包含完整的PEM格式密钥。\n"
                    f"请从支付宝开放平台获取支付宝公钥并正确填写到文件中。\n"
                    f"密钥格式示例：\n"
                    f"-----BEGIN PUBLIC KEY-----\n"
                    f"密钥内容（多行base64编码）\n"
                    f"-----END PUBLIC KEY-----"
                )
            raise



        if debug is True:
            # 新版本沙箱环境使用新地址
            self.__gateway = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"
        else:
            self.__gateway = "https://openapi.alipay.com/gateway.do"



    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
        """
        创建支付宝支付请求
        
        Args:
            subject: 订单标题
            out_trade_no: 商户订单号
            total_amount: 订单总金额（字符串格式，如 "0.01"）
            return_url: 支付完成后的跳转地址
            **kwargs: 其他业务参数
        """
        # 验证金额
        try:
            amount_float = float(total_amount)
            if amount_float <= 0:
                raise ValueError(f"订单金额必须大于0，当前金额：{total_amount}")
            # 确保金额格式为两位小数
            total_amount = f"{amount_float:.2f}"
        except (ValueError, TypeError) as e:
            if isinstance(e, ValueError) and "订单金额必须大于0" in str(e):
                raise
            raise ValueError(f"订单金额格式错误：{total_amount}，必须是有效的数字")

        biz_content = {

            "subject": subject,

            "out_trade_no": out_trade_no,

            "total_amount": total_amount,

            "product_code": "FAST_INSTANT_TRADE_PAY",

            # "qr_pay_mode":4

        }



        biz_content.update(kwargs)

        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)

        return self.sign_data(data)

    

     #查询接口

    def query_pay(self, out_trade_no,return_url=None, **kwargs):

        biz_content = {

            "out_trade_no": out_trade_no,

           

            # "product_code": "FAST_INSTANT_TRADE_PAY",

            # "qr_pay_mode":4

        }



        biz_content.update(kwargs)

        data = self.build_body("alipay.trade.query", biz_content, self.return_url)

        return self.sign_data(data)



    def build_body(self, method, biz_content, return_url=None):

        data = {

            "app_id": self.appid,

            "method": method,

            "charset": "utf-8",

            "sign_type": "RSA2",

            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),

            "version": "1.0",

            "biz_content": biz_content

        }



        if return_url is not None:

            data["notify_url"] = self.app_notify_url

            data["return_url"] = self.return_url



        return data



    def sign_data(self, data):
        """
        对数据进行签名并生成最终的URL参数字符串
        
        支付宝签名流程：
        1. 移除sign字段（如果存在）
        2. 对参数按key排序
        3. 将字典类型值转为JSON字符串
        4. 拼接成 key=value&key=value 格式（不编码）
        5. 对拼接字符串进行RSA2签名
        6. 将签名base64编码
        7. 对所有参数（包括sign）进行URL编码后拼接
        """
        # 创建数据副本，避免修改原始数据
        sign_data = data.copy()
        sign_data.pop("sign", None)

        # 排序后的参数列表（ordered_data会修改sign_data，将dict转为JSON字符串）
        unsigned_items = self.ordered_data(sign_data)

        # 拼接未编码的字符串用于签名
        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)

        # 对未编码字符串进行签名
        sign = self.sign(unsigned_string.encode("utf-8"))

        # 对所有参数（包括sign）进行URL编码后拼接
        quoted_string = "&".join("{0}={1}".format(k, quote_plus(str(v))) for k, v in unsigned_items)

        # 获得最终的订单信息字符串（包含签名）
        signed_string = quoted_string + "&sign=" + quote_plus(sign)

        return signed_string



    def ordered_data(self, data):
        """
        对数据进行排序和格式化
        注意：这里会修改原始data字典，但这是支付宝签名规范要求的
        """
        complex_keys = []
        for key, value in data.items():
            if isinstance(value, dict):
                complex_keys.append(key)

        # 将字典类型的数据dump出来（不编码，直接转为JSON字符串）
        for key in complex_keys:
            data[key] = json.dumps(data[key], separators=(',', ':'), ensure_ascii=False)
        
        # 返回排序后的键值对列表
        return sorted([(k, v) for k, v in data.items()])



    def sign(self, unsigned_string):

        # 开始计算签名

        key = self.app_private_key

        signer = PKCS1_v1_5.new(key)

        signature = signer.sign(SHA256.new(unsigned_string))

        # base64 编码，转换为unicode表示并移除回车

        sign = encodebytes(signature).decode("utf8").replace("\n", "")

        return sign



    def _verify(self, raw_content, signature):

        # 开始计算签名

        key = self.alipay_public_key

        signer = PKCS1_v1_5.new(key)

        digest = SHA256.new()

        digest.update(raw_content.encode("utf8"))

        if signer.verify(digest, decodebytes(signature.encode("utf8"))):

            return True

        return False



    def verify(self, data, signature):

        if "sign_type" in data:

            sign_type = data.pop("sign_type")

        # 排序后的字符串

        unsigned_items = self.ordered_data(data)

        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)

        return self._verify(message, signature)

